<?php

namespace StarCloud\Models;

use Phalcon\Db\Adapter\Pdo\Mysql;
use Phalcon\Db\Column;
use StarCloud\Library\Exception;
use Phalcon\Di;
use Phalcon\Mvc\Model;
use Framework\Library\Paginator;
use Phalcon\Paginator\Adapter\QueryBuilder;
use StarCloud\Library\SentryException;

class Base extends Model
{
    const UID_MIN = 1000000000;
    const UID_MAX = 9999999999;
    const UID_FIELD = 'uid';

    const IS_DELETE_YES = 1;
    const IS_DELETE_NO = 0;

    const STATUS_ENABLE = 1;
    const STATUS_DISABLE = 0;

    public function initialize()
    {
        if (isset($this->tableName)) {
            $this->setSource($this->tableName);
        } else {
            $modelName = strtolower(preg_replace('/(?<!^)[A-Z]/', '_$0', substr(get_called_class(), strrpos(get_called_class(), '\\') + 1)));
            $this->setSource($modelName . 's');
        }

        if (isset($this->canBeEmptyStringColumns) && is_array($this->canBeEmptyStringColumns)) {
            $this->allowEmptyStringValues($this->canBeEmptyStringColumns);
        }

        $this->useDynamicUpdate(true);
    }

    public static function getPaginatorArrayResultByUid($uid, $page, $count, $orderBy = 'id DESC')
    {
        $parameter = [
            'conditions' => 'uid = :uid:',
            'bind' => ['uid' => $uid],
            'bindTypes' => ['uid' => Column::BIND_PARAM_INT],
        ];

        return self::getPaginatorArrayResult($parameter, $page, $count, $orderBy);
    }

    public static function getPaginatorArrayResult(array $parameter, $page, $count, $orderBy = 'id DESC')
    {
        $builder = Di::getDefault()->get('modelsManager')->createBuilder()
            ->columns($parameter['columns'] ?? null)
            ->from(get_called_class())
            ->where($parameter['conditions'] ?? null, $parameter['bind'] ?? null, $parameter['bindTypes'] ?? null)
            ->groupBy($parameter['group'] ?? null)
            ->orderBy($orderBy);

        $paginator = new QueryBuilder([
            'builder' => $builder,
            'page' => $page,
            'limit' => $count,
        ]);

        return Paginator::paginatorBuilderResultToArray($paginator->paginate(), 'data', $count);
    }

    public static function batchUpdate($parameter, $data)
    {
        $records = self::find($parameter);
        foreach ($records as $record) {
            $record->update($data);
        }
    }

    public function toArray($columns = null)
    {
        $result = parent::toArray($columns);
        $metadata = $this->getDI()->get('modelsMetadata');
        $types = $metadata->getDataTypes($this);
        foreach ($types as $attribute => $type) {
            if (isset($result[$attribute]) && !is_null($result[$attribute])) {
                if ($type == Column::TYPE_INTEGER || $type == Column::TYPE_BIGINTEGER) {
                    $result[$attribute] = (int)$result[$attribute];
                } elseif ($type == Column::TYPE_DECIMAL) {
                    $result[$attribute] = (float)$result[$attribute];
                }
            }
        }

        return $result;
    }

    public function beforeValidationOnCreate()
    {
        $this->created_at = date('Y-m-d H:i:s');
        $this->updated_at = date('Y-m-d H:i:s');
    }

    public function beforeValidationOnUpdate()
    {
        $this->updated_at = date('Y-m-d H:i:s');
    }

    public function save($data = null, $whiteList = null)
    {
        try {
            parent::save($data, $whiteList);
        } catch (\Exception $e) {
            $this->dealWithException($e);
        }

        foreach ($this->getMessages() as $message) {
            throw new SentryException('10005', $message->getMessage());
        }
    }

    public function delete()
    {
        try {
            parent::delete();
        } catch (\Exception $e) {
            $this->dealWithException($e);
        }

        foreach ($this->getMessages() as $message) {
            throw new SentryException('10006', $message->getMessage());
        }
    }

    private function dealWithException(\Exception $e)
    {
        Di::getDefault()->get('logger')->error('DB Error:' . $e->getCode() . ':' . $e->getMessage());

        if (ENVIRONMENT != 'production') {
            throw $e;
        } else {
            throw new Exception('10001');
        }
    }

    public function __call($method, $arguments)
    {
        if (in_array(($methodType = substr($method, 0, 3)), ['get', 'has', 'set'])) {
            if ($methodType == 'set' && count($arguments) != 1) {
                throw new Exception('10001');
            }
        } else {
            throw new Exception('10001');
        }

        $propertyName = strtolower(preg_replace('/(?<=[a-z])(?=[A-Z])/', '_', substr($method, 3)));
        if (!property_exists($this, $propertyName)) {
            throw new Exception('10001');
        }
        if ($methodType == 'set' && in_array($propertyName, ['id', 'created_at', 'updated_at'])) {
            throw new Exception('10001');
        }

        switch ($methodType) {
            case 'get':
                return $this->$propertyName;
            case 'set':
                return $this->$propertyName = $arguments[0];
            case 'has':
                return !empty($this->$propertyName);
            default:
                throw new Exception('10001');
        }
    }

    public static function getDb(): Mysql
    {
        return Di::getDefault()->getShared('db');
    }

    public static function findFirstByIdForUpdate($id)
    {
        return self::findFirst([
            'conditions' => 'id = :id:',
            'bind' => ['id' => $id],
            'bindTypes' => ['id' => Column::BIND_PARAM_INT],
            'for_update' => true,
        ]);
    }

    public static function implodeIntArrayToString(array $intArr)
    {
        return ' (' . implode(',', $intArr) . ') ';
    }

    public static function implodeStringArrayToString(array $stringArr)
    {
        return ' ("' . implode('","', $stringArr) . '") ';
    }

    public function softDelete()
    {
        $this->is_delete = self::IS_DELETE_YES;
        $this->save();

        return $this;
    }

    public function switchStatus()
    {
        if ($this->status == self::STATUS_DISABLE) {
            $this->status = self::STATUS_ENABLE;
        } else {
            $this->status = self::STATUS_DISABLE;
        }

        $this->save();

        return $this;
    }

    /**
     * @param $returnName:组装后的数据名
     * @param $indexKey:主表键名
     * @param array $needFillArrays:主表结果集
     * @param string $foreignKey:关联表键名
     * @return array
     */
    public static function fillArrayRelation($returnName, $indexKey, array $needFillArrays, $foreignKey = 'id'): array
    {
        $needFillArraysBak = $needFillArrays;
        $hasDataKey = false;
        if (isset($needFillArrays['data'])) {
            $hasDataKey = true;
            $needFillArrays = $needFillArrays['data'];
        }

        $ids = array_column($needFillArrays, $indexKey);

        if (empty($ids)) {
            return $needFillArraysBak;
        }

        $roles = array_column(self::getByKey($ids, $foreignKey), null, $foreignKey);
        foreach ($needFillArrays as &$needFillArray) {
            $needFillArray[$returnName] = $roles[$needFillArray[$indexKey]] ?? [];
        }

        if ($hasDataKey) {
            $needFillArraysBak['data'] = $needFillArrays;
            return $needFillArraysBak;
        } else {
            return $needFillArrays;
        }
    }

    protected static function getByKey($ids, $foreignKey)
    {
        return self::find([
            'conditions' => "{$foreignKey} IN" . self::implodeIntArrayToString($ids),
        ])->toArray();
    }

    protected $is_delete;
    protected $status;
}
